using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using InfiniTec.Threading;

namespace InfiniTec.IO
{
	/// <summary>
	/// This class manages the allocation of byte buffers. 
	/// </summary>
	/// <remarks>
	/// <p>
	///	Normally, one would simply do a <c>byte[] buffer = new byte[1000000]</c>. While this is
	///	not entirely a bad idea, it certainly can become one when you do this often enough, because
	///	your memory consumption rise with each request, even if you don't have any references to 
	///	the arrays any longer.
	///	</p>
	///	<p>
	///	The reasons for this is that such large objects are stored on the Large Object Heap.
	/// The objects on this heap will eventually be gargabe collected, but this
	/// will occour only when the runtime does a full collection. Which will hopefully not occur
	/// so often as it is a quite expensive procedure.
	///	</p>
	///	<p>
	/// This class allows you to re-use previously allocated buffers via the <see cref="GetBuffer"/>
	/// method. And once you are done with the buffer you can safely return it by using
	/// <see cref="RecycleBuffer"/>.
	/// </p>
	/// <p>Note that this manager does not prevent the garbage collection from collecting
	/// these recycled buffers, since they are stored within a <see cref="WeakReference"/></p>
	/// </remarks>
	public static class BufferProvider
	{
		/// <summary>
		/// This is the treshold for so-called Large Objects. These objects
		/// are not stored within the three standard Heaps. All objects with a size
		/// greater 85000 bytes are stored on the Large Object Heap. This heap is collected
		/// along with the the Generation-2 heap.
		/// </summary>
		private const int LargeObjectTreshold = 85000;

		/// <summary>
		/// This <see cref="ArrayList"/> stores the currently available buffers.
		/// </summary>
		private static readonly List<ByteBag> _StoredBuffers = new List<ByteBag>();

		private static readonly SpinWaitLock _BufferLock = new SpinWaitLock();
	
		/// <summary>
		/// This mehtod clears the given buffer and adds it to the collection of available buffers.
		/// </summary>
		/// <param name="buffer">The buffer to recycle</param>
		public static void RecycleBuffer(byte[] buffer)
		{
			if (buffer == null)
			{
				throw new ArgumentNullException("buffer");
			}

			if (buffer.Length < LargeObjectTreshold) return;
			ClearBuffer(buffer);

			try
			{
				_BufferLock.Enter();
				_StoredBuffers.Add(new ByteBag(buffer));
				_StoredBuffers.Sort();
			}
			finally
			{
				_BufferLock.Exit();
			}
		}

		/// <summary>
		/// Zeros-out the buffer via <see cref="Array.Clear"/>-
		/// </summary>
		/// <param name="buffer">The buffer to clear.</param>
		private static void ClearBuffer(byte[] buffer)
		{
			if (buffer == null)
			{
				throw new ArgumentNullException("buffer");
			}
			Array.Clear(buffer, 0, buffer.Length);
		}

		/// <summary>
		/// Returs a buffer with a size between size and size*2. If currently no such buffer
		/// is available, a new buffer is allocated.
		/// </summary>
		/// <param name="size">The required size of the buffer.</param>
		/// <returns>The new buffer</returns>
		public static byte[] GetBuffer(long size)
		{
			if (size < LargeObjectTreshold) return new byte[size];
			if (size < 0)
			{
				throw new ArgumentOutOfRangeException("size", "size must be equal to or greater than zero");
			}

			try
			{
				_BufferLock.Enter();
				var result = _StoredBuffers.FirstOrDefault(bag => bag.Size > size);

				if (result == null || result.Size > size*2)
				{
					return new byte[size];
				}

				var buffer = result.GetBuffer();

				return buffer ?? new byte[size];
			}
			finally
			{
				_BufferLock.Exit();
			}
		}
	}
}